#ifndef DICTIONARY_H_
#define DICTIONARY_H_

#include "util/LinkedList.h"

namespace util {

/*
 * Clase para contener objetos de la forma clave/valor. Esta implementada con el
 * metodo foo.compare(bar) para comparar claves (mas que nada para poder usar
 * std::string como clave).
 */
template<class K, class V> class Dictionary {
public:
	Dictionary();

	virtual ~Dictionary();
	/* Agrega un valor y lo asocia con una determinada key. */
	virtual void put(K key, V* value);

	/* Obtiene el valor correspondiente a la key pasada por parametro. */
	virtual V* get(K key) throw (ElementNotFoundException);

	/* Elimina un valor correspondiente a la key pasada por parametro. */
	virtual V* remove(K key) throw (ElementNotFoundException);

	/* Indica si el hashmap contiene la key pasada por parametro. */
	virtual bool containsKey(K key);

	/* Indica si el hashmap contiene el valor pasado por parametro. */
	virtual bool containsValue(V* value);

	/* Devuelve la cantidad de items (key-values) que hay en el hash. */
	virtual int size();

	/* Indica si el hash esta vacio. */
	virtual bool isEmpty();

	/* Elimina todos los elementos del hashmap. */
	virtual void clear();

	virtual LinkedList<K> keySet() {
		return this->keyList;
	}
private:
	/*
	 * Clase privada para las entradas del mapa.
	 */
	class Entry {
	public:
		/* Constructor. Recibe la clave y el valor de la entrada al mapa. */
		Entry(K key, V* value);

		/* Destructor. */
		~Entry();

		/* Metodo para recuperar la clave del elemento del mapa. */
		K getKey();

		/* Metodo para recuperar el valor del elemento del mapa. */
		V* getValue();

		/* Metodo para setear el valor de la entrada al mapa. */
		void setValue(V* value);

	private:
		K key;
		V* value;
	};

	/*
	 * Clase privada que funciona de matcher para encontrar en la lista de
	 * elementos un elemento pedido.
	 */
	class EntryMatcher: public Matcher<Entry> {
	public:
		/* Constructor, recibe la clave a buscar. */
		EntryMatcher(K key) : currentKey(key) { }
		/* Destructor. */
		~EntryMatcher() {}
		/*
		 * Metodo para matchear con otro objeto entrada.
		 * NOVA: Kmplementada con el metodo compare para poder usarla con
		 * std::string.
		 */
		bool matches(Entry* entryToCompare){
			return ((entryToCompare != NULL) && (entryToCompare->getKey().compare(this->currentKey) == 0));
		}

	private:
		K currentKey;
	};
	LinkedList<K> keyList;
	LinkedList<Entry> dataList;
};

template<class K, class V> Dictionary<K, V>::Dictionary() {}

template<class K, class V> Dictionary<K, V>::~Dictionary() {}

template<class K, class V> void Dictionary<K, V>::put(K key, V* value) {
	EntryMatcher matcher(key);
	Entry* entry = NULL;
	try {
		entry = this->dataList.search(&matcher);
		entry->setValue(value);
	} catch (ElementNotFoundException &e) {
		entry = new Entry(key, value);
		this->dataList.append(entry);
		this->keyList.append(new K(key));
	}
}
template<class K, class V> V* Dictionary<K, V>::get(K key)
		throw (ElementNotFoundException) {
	EntryMatcher matcher(key);
	try {
		return (this->dataList.search(&matcher))->getValue();
	} catch (ElementNotFoundException &e) {
		throw ElementNotFoundException("La clave no existe en el mapa.");
	}
}

template<class K, class V> V* Dictionary<K, V>::remove(K key)
		throw (ElementNotFoundException) {
	EntryMatcher matcher(key);
	delete this->keyList.remove(&key);
	Entry* ret = this->dataList.remove(&matcher);
	V* value = ret->getValue();
	delete ret;
	return value;
}

template<class K, class V> bool Dictionary<K, V>::containsKey(K key) {
	EntryMatcher matcher(key);
	return this->dataList.contains(&matcher);
}

template<class K, class V> bool Dictionary<K, V>::containsValue(V* value) {
	// TODO implement me...
	throw Exception("Implement me!");
}

template<class K, class V> int Dictionary<K, V>::size() {
	return this->dataList.count();
}

template<class K, class V> bool Dictionary<K, V>::isEmpty() {
	return this->dataList.isEmpty();
}

template<class K, class V> void Dictionary<K, V>::clear() {
	while(!this->keyList.isEmpty()){
		delete this->dataList.removeFirst();
		delete this->keyList.removeFirst();
	}
}

//template<class K, class V> LinkedList<K> Dictionary<K, V>::keySet() {
//	LinkedList<K> lista;
//	// LinkedList<Entry>::Iterator; // dataList;
////	while(it.hasNext()) {
////		lista.add(0, it.next()->getKey());
////	}
//	return lista;
//}
/* Implementacion de Entry */

template<class K, class V> Dictionary<K, V>::Entry::Entry(K key, V* value) : key(key) {
	this->value = value;
}
template<class K, class V> Dictionary<K, V>::Entry::~Entry() {}
template<class K, class V> K Dictionary<K, V>::Entry::getKey() {
	return this->key;
}
template<class K, class V> V* Dictionary<K, V>::Entry::getValue() {
	return this->value;
}
template<class K, class V> void Dictionary<K, V>::Entry::setValue(V* value) {
	this->value = value;
}

}


#endif /* DICTIONARY_H_ */
